// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This is a small utility that snarfs the server time from the
// response headers of an http/https HEAD request and compares it to
// the local time.
//
// TODO(akalin): Also snarf the server time from the TLS handshake, if
// any (http://crbug.com/146090).

#include <cstdio>
#include <cstdlib>
#include <memory>
#include <string>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/format_macros.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/http/http_response_headers.h"
#include "net/log/net_log.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
#include "url/gurl.h"

#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#elif defined(OS_LINUX)
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service_fixed.h"
#endif

using base::UTF16ToUTF8;

namespace {

// base::TimeTicks::Now() is documented to have a resolution of
// ~1-15ms.
const int64_t kTicksResolutionMs = 15;

// For the sources that are supported (HTTP date headers, TLS
// handshake), the resolution of the server time is 1 second.
const int64_t kServerTimeResolutionMs = 1000;

// Assume base::Time::Now() has the same resolution as
// base::TimeTicks::Now().
//
// TODO(akalin): Figure out the real resolution.
const int64_t kTimeResolutionMs = kTicksResolutionMs;

// Simply quits the current message loop when finished.  Used to make
// URLFetcher synchronous.
class QuitDelegate : public net::URLFetcherDelegate {
public:
    QuitDelegate() { }

    ~QuitDelegate() override { }

    // net::URLFetcherDelegate implementation.
    void OnURLFetchComplete(const net::URLFetcher* source) override
    {
        base::MessageLoop::current()->QuitWhenIdle();
    }

    void OnURLFetchDownloadProgress(const net::URLFetcher* source,
        int64_t current,
        int64_t total) override
    {
        NOTREACHED();
    }

    void OnURLFetchUploadProgress(const net::URLFetcher* source,
        int64_t current,
        int64_t total) override
    {
        NOTREACHED();
    }

private:
    DISALLOW_COPY_AND_ASSIGN(QuitDelegate);
};

// NetLog::ThreadSafeObserver implementation that simply prints events
// to the logs.
class PrintingLogObserver : public net::NetLog::ThreadSafeObserver {
public:
    PrintingLogObserver() { }

    ~PrintingLogObserver() override
    {
        // This is guaranteed to be safe as this program is single threaded.
        net_log()->DeprecatedRemoveObserver(this);
    }

    // NetLog::ThreadSafeObserver implementation:
    void OnAddEntry(const net::NetLog::Entry& entry) override
    {
        // The log level of the entry is unknown, so just assume it maps
        // to VLOG(1).
        if (!VLOG_IS_ON(1))
            return;

        const char* const source_type = net::NetLog::SourceTypeToString(entry.source().type);
        const char* const event_type = net::NetLog::EventTypeToString(entry.type());
        const char* const event_phase = net::NetLog::EventPhaseToString(entry.phase());
        std::unique_ptr<base::Value> params(entry.ParametersToValue());
        std::string params_str;
        if (params.get()) {
            base::JSONWriter::Write(*params, &params_str);
            params_str.insert(0, ": ");
        }

        VLOG(1) << source_type << "(" << entry.source().id << "): "
                << event_type << ": " << event_phase << params_str;
    }

private:
    DISALLOW_COPY_AND_ASSIGN(PrintingLogObserver);
};

// Builds a URLRequestContext assuming there's only a single loop.
std::unique_ptr<net::URLRequestContext> BuildURLRequestContext(
    net::NetLog* net_log)
{
    net::URLRequestContextBuilder builder;
#if defined(OS_LINUX)
    // On Linux, use a fixed ProxyConfigService, since the default one
    // depends on glib.
    //
    // TODO(akalin): Remove this once http://crbug.com/146421 is fixed.
    builder.set_proxy_config_service(
        base::WrapUnique(new net::ProxyConfigServiceFixed(net::ProxyConfig())));
#endif
    std::unique_ptr<net::URLRequestContext> context(builder.Build());
    context->set_net_log(net_log);
    return context;
}

// Assuming that the time |server_time| was received from a server,
// that the request for the server was started on |start_ticks|, and
// that it ended on |end_ticks|, fills |server_now| with an estimate
// of the current time and |server_now_uncertainty| with a
// conservative estimate of the uncertainty.
void EstimateServerTimeNow(base::Time server_time,
    base::TimeTicks start_ticks,
    base::TimeTicks end_ticks,
    base::Time* server_now,
    base::TimeDelta* server_now_uncertainty)
{
    const base::TimeDelta delta_ticks = end_ticks - start_ticks;
    const base::TimeTicks mid_ticks = start_ticks + delta_ticks / 2;
    const base::TimeDelta estimated_elapsed = base::TimeTicks::Now() - mid_ticks;

    *server_now = server_time + estimated_elapsed;

    *server_now_uncertainty = base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs) + delta_ticks + 3 * base::TimeDelta::FromMilliseconds(kTicksResolutionMs);
}

// Assuming that the time of the server is |server_now| with
// uncertainty |server_now_uncertainty| and that the local time is
// |now|, fills |skew| with the skew of the local clock (i.e., add
// |*skew| to a client time to get a server time) and
// |skew_uncertainty| with a conservative estimate of the uncertainty.
void EstimateSkew(base::Time server_now,
    base::TimeDelta server_now_uncertainty,
    base::Time now,
    base::TimeDelta now_uncertainty,
    base::TimeDelta* skew,
    base::TimeDelta* skew_uncertainty)
{
    *skew = server_now - now;
    *skew_uncertainty = server_now_uncertainty + now_uncertainty;
}

} // namespace

int main(int argc, char* argv[])
{
#if defined(OS_MACOSX)
    base::mac::ScopedNSAutoreleasePool pool;
#endif

    base::AtExitManager exit_manager;
    base::CommandLine::Init(argc, argv);
    logging::LoggingSettings settings;
    settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
    logging::InitLogging(settings);

    const base::CommandLine& parsed_command_line = *base::CommandLine::ForCurrentProcess();
    GURL url(parsed_command_line.GetSwitchValueASCII("url"));
    if (!url.is_valid() || (url.scheme() != "http" && url.scheme() != "https")) {
        std::fprintf(
            stderr,
            "Usage: %s --url=[http|https]://www.example.com [--v=[1|2]]\n",
            argv[0]);
        return EXIT_FAILURE;
    }

    base::MessageLoopForIO main_loop;

    // NOTE: A NetworkChangeNotifier could be instantiated here, but
    // that interferes with the request that will be sent; some
    // implementations always send out an OnIPAddressChanged() message,
    // which causes the DNS resolution to abort.  It's simpler to just
    // not instantiate one, since only a single request is sent anyway.

    // The declaration order for net_log and printing_log_observer is
    // important. The destructor of PrintingLogObserver removes itself
    // from net_log, so net_log must be available for entire lifetime of
    // printing_log_observer.
    net::NetLog net_log;
    PrintingLogObserver printing_log_observer;
    net_log.DeprecatedAddObserver(&printing_log_observer,
        net::NetLogCaptureMode::IncludeSocketBytes());

    QuitDelegate delegate;
    std::unique_ptr<net::URLFetcher> fetcher = net::URLFetcher::Create(url, net::URLFetcher::HEAD, &delegate);
    std::unique_ptr<net::URLRequestContext> url_request_context(
        BuildURLRequestContext(&net_log));
    fetcher->SetRequestContext(
        // Since there's only a single thread, there's no need to worry
        // about when the URLRequestContext gets created.
        // The URLFetcher will take a reference on the object, and hence
        // implicitly take ownership.
        new net::TrivialURLRequestContextGetter(url_request_context.get(),
            main_loop.task_runner()));
    const base::Time start_time = base::Time::Now();
    const base::TimeTicks start_ticks = base::TimeTicks::Now();

    fetcher->Start();
    std::printf(
        "Request started at %s (ticks = %" PRId64 ")\n",
        UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(start_time)).c_str(),
        start_ticks.ToInternalValue());

    // |delegate| quits |main_loop| when the request is done.
    main_loop.Run();

    const base::Time end_time = base::Time::Now();
    const base::TimeTicks end_ticks = base::TimeTicks::Now();

    std::printf(
        "Request ended at %s (ticks = %" PRId64 ")\n",
        UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(end_time)).c_str(),
        end_ticks.ToInternalValue());

    const int64_t delta_ticks_internal = end_ticks.ToInternalValue() - start_ticks.ToInternalValue();
    const base::TimeDelta delta_ticks = end_ticks - start_ticks;

    std::printf(
        "Request took %" PRId64 " ticks (%.2f ms)\n",
        delta_ticks_internal, delta_ticks.InMillisecondsF());

    const net::URLRequestStatus status = fetcher->GetStatus();
    if (status.status() != net::URLRequestStatus::SUCCESS) {
        LOG(ERROR) << "Request failed with error code: "
                   << net::ErrorToString(status.error());
        return EXIT_FAILURE;
    }

    const net::HttpResponseHeaders* const headers = fetcher->GetResponseHeaders();
    if (!headers) {
        LOG(ERROR) << "Response does not have any headers";
        return EXIT_FAILURE;
    }

    size_t iter = 0;
    std::string date_header;
    while (headers->EnumerateHeader(&iter, "Date", &date_header)) {
        std::printf("Got date header: %s\n", date_header.c_str());
    }

    base::Time server_time;
    if (!headers->GetDateValue(&server_time)) {
        LOG(ERROR) << "Could not parse time from server response headers";
        return EXIT_FAILURE;
    }

    std::printf(
        "Got time %s from server\n",
        UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_time)).c_str());

    base::Time server_now;
    base::TimeDelta server_now_uncertainty;
    EstimateServerTimeNow(server_time, start_ticks, end_ticks,
        &server_now, &server_now_uncertainty);
    base::Time now = base::Time::Now();

    std::printf(
        "According to the server, it is now %s with uncertainty %.2f ms\n",
        UTF16ToUTF8(base::TimeFormatFriendlyDateAndTime(server_now)).c_str(),
        server_now_uncertainty.InMillisecondsF());

    base::TimeDelta skew;
    base::TimeDelta skew_uncertainty;
    EstimateSkew(server_now, server_now_uncertainty, now,
        base::TimeDelta::FromMilliseconds(kTimeResolutionMs),
        &skew, &skew_uncertainty);

    std::printf(
        "An estimate for the local clock skew is %.2f ms with "
        "uncertainty %.2f ms\n",
        skew.InMillisecondsF(),
        skew_uncertainty.InMillisecondsF());

    return EXIT_SUCCESS;
}
